package com.starhub.system.database.manager;

import com.starhub.system.database.config.DatabaseUpgradeConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.HashSet;
import java.util.stream.Collectors;

/**
 * SQL文件管理器
 */
@Slf4j
@Component
public class SqlFileManager {
    
    @Autowired
    private DatabaseUpgradeConfig config;
    
    @Autowired
    private ResourceLoader resourceLoader;
    
    // SQL文件命名规范常量
    private static final String SQL_FILE_PREFIX = "sh";
    private static final String SQL_FILE_EXTENSION = ".sql";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    
    /**
     * SQL文件信息类，用于排序
     */
    private static class SqlFileInfo implements Comparable<SqlFileInfo> {
        private final String fileName;
        private final LocalDate fileDate;
        private final int sequenceNumber;
        private final File file;
        
        public SqlFileInfo(String fileName, LocalDate fileDate, int sequenceNumber, File file) {
            this.fileName = fileName;
            this.fileDate = fileDate;
            this.sequenceNumber = sequenceNumber;
            this.file = file;
        }
        
        @Override
        public int compareTo(SqlFileInfo other) {
            // 首先按日期排序
            int dateCompare = this.fileDate.compareTo(other.fileDate);
            if (dateCompare != 0) {
                return dateCompare;
            }
            // 日期相同时按流水号排序
            return Integer.compare(this.sequenceNumber, other.sequenceNumber);
        }
        
        public String getFileName() { return fileName; }
        public LocalDate getFileDate() { return fileDate; }
        public int getSequenceNumber() { return sequenceNumber; }
        public File getFile() { return file; }
        
        @Override
        public String toString() {
            return String.format("%s (日期: %s, 流水号: %02d)", fileName, fileDate, sequenceNumber);
        }
    }
    
    /**
     * 验证SQL文件名是否符合规范
     * 规范格式：sh + YYYYMMDD + 流水号(2位) + .sql
     * 例如：sh2025081901.sql
     */
    private boolean isValidSqlFileName(String fileName) {
        if (fileName == null || fileName.length() < 15) {
            return false;
        }
        
        // 检查文件扩展名
        if (!fileName.toLowerCase().endsWith(SQL_FILE_EXTENSION)) {
            return false;
        }
        
        // 检查前缀
        if (!fileName.startsWith(SQL_FILE_PREFIX)) {
            return false;
        }
        
        // 提取日期部分（前缀后的8位数字）
        String datePart = fileName.substring(SQL_FILE_PREFIX.length(), SQL_FILE_PREFIX.length() + 8);
        
        // 验证日期格式
        try {
            LocalDate.parse(datePart, DATE_FORMATTER);
        } catch (DateTimeParseException e) {
            return false;
        }
        
        // 提取流水号部分（日期后的2位数字）
        String sequencePart = fileName.substring(SQL_FILE_PREFIX.length() + 8, SQL_FILE_PREFIX.length() + 10);
        
        // 验证流水号是否为数字
        try {
            int sequence = Integer.parseInt(sequencePart);
            if (sequence < 0 || sequence > 99) {
                return false;
            }
        } catch (NumberFormatException e) {
            return false;
        }
        
        return true;
    }
    
    /**
     * 解析SQL文件名，提取日期和流水号信息
     */
    private SqlFileInfo parseSqlFileName(String fileName, File file) {
        try {
            // 提取日期部分
            String datePart = fileName.substring(SQL_FILE_PREFIX.length(), SQL_FILE_PREFIX.length() + 8);
            LocalDate fileDate = LocalDate.parse(datePart, DATE_FORMATTER);
            
            // 提取流水号部分
            String sequencePart = fileName.substring(SQL_FILE_PREFIX.length() + 8, SQL_FILE_PREFIX.length() + 10);
            int sequenceNumber = Integer.parseInt(sequencePart);
            
            return new SqlFileInfo(fileName, fileDate, sequenceNumber, file);
        } catch (Exception e) {
            log.warn("解析SQL文件名失败: {}, 错误: {}", fileName, e.getMessage());
            return null;
        }
    }
    
    /**
     * 策略1: 使用PathMatchingResourcePatternResolver扫描
     */
    private List<File> getSqlFilesByPattern(String databaseType) {
        List<File> sqlFiles = new ArrayList<>();
        try {
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(resourceLoader);
            String pattern = "classpath:" + config.getUpgradePath() + "/" + databaseType + "/*.sql";
            
            Resource[] resources = resolver.getResources(pattern);
            
            if (resources.length > 0) {
                // 按文件名排序
                List<Resource> sortedResources = Arrays.asList(resources);
                sortedResources.sort((r1, r2) -> {
                    try {
                        return r1.getFilename().compareTo(r2.getFilename());
                    } catch (Exception e) {
                        return 0;
                    }
                });
                
                // 创建File对象（用于标识）
                for (Resource resource : sortedResources) {
                    String fileName = resource.getFilename();
                    if (fileName != null && fileName.toLowerCase().endsWith(".sql")) {
                        File sqlFile = new File(config.getUpgradePath() + "/" + databaseType + "/" + fileName);
                        sqlFiles.add(sqlFile);
                    }
                }
            }
            
        } catch (IOException e) {
            log.debug("策略1失败: {}", e.getMessage());
        }
        return sqlFiles;
    }
    
    /**
     * 策略2: 使用ClassLoader直接扫描
     */
    private List<File> getSqlFilesByClassLoader(String databaseType) {
        List<File> sqlFiles = new ArrayList<>();
        try {
            String path = config.getUpgradePath() + "/" + databaseType;
            
            // 获取ClassLoader
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            if (classLoader == null) {
                classLoader = getClass().getClassLoader();
            }
            
            // 尝试获取目录下的所有文件
            java.net.URL url = classLoader.getResource(path);
            if (url != null) {
                // 如果是jar包，需要特殊处理
                if ("jar".equals(url.getProtocol())) {
                    // 这里可以添加JAR包扫描逻辑
                } else {
                    // 如果是文件系统，直接扫描
                    File directory = new File(url.toURI());
                    if (directory.exists() && directory.isDirectory()) {
                        File[] files = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".sql"));
                        if (files != null) {
                            for (File file : files) {
                                sqlFiles.add(file);
                            }
                        }
                    }
                }
            }
            
        } catch (Exception e) {
            log.debug("策略2失败: {}", e.getMessage());
        }
        return sqlFiles;
    }
    
    /**
     * 策略3: 使用ResourceLoader扫描目录
     */
    private List<File> getSqlFilesByResourceLoader(String databaseType) {
        List<File> sqlFiles = new ArrayList<>();
        try {
            String path = config.getUpgradePath() + "/" + databaseType;
            
            // 尝试获取目录资源
            Resource dirResource = resourceLoader.getResource("classpath:" + path);
            if (dirResource.exists()) {
                // 尝试列出目录内容
                if (dirResource.getFile().isDirectory()) {
                    File directory = dirResource.getFile();
                    File[] files = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".sql"));
                    if (files != null) {
                        for (File file : files) {
                            sqlFiles.add(file);
                        }
                    }
                }
            }
            
        } catch (Exception e) {
            log.debug("策略3失败: {}", e.getMessage());
        }
        return sqlFiles;
    }
    
    /**
     * 策略4: 硬编码测试方法（用于调试）
     */
    private List<File> getSqlFilesByHardcoded(String databaseType) {
        List<File> sqlFiles = new ArrayList<>();
        try {
            // 硬编码一些可能的路径进行测试
            String[] testPaths = {
                "sql/upgrade/" + databaseType + "/V1.0.21__test_database_upgrade.sql",
                "sql/upgrade/" + databaseType + "/V1.0.22__simple_test.sql",
                "sql/upgrade/" + databaseType + "/V1.0.24__test_pattern_resolver.sql"
            };
            
            for (String testPath : testPaths) {
                Resource resource = resourceLoader.getResource("classpath:" + testPath);
                if (resource.exists()) {
                    File sqlFile = new File(testPath);
                    sqlFiles.add(sqlFile);
                }
            }
            
        } catch (Exception e) {
            log.debug("策略4失败: {}", e.getMessage());
        }
        return sqlFiles;
    }
    
    /**
     * 获取指定数据库类型的所有SQL文件
     */
    public List<File> getSqlFiles(String databaseType) {
        log.info("开始扫描数据库类型 [{}] 的SQL文件", databaseType);
        
        // 收集所有策略的结果
        Map<String, List<File>> strategyResults = new HashMap<>();
        
        // 策略1: 使用PathMatchingResourcePatternResolver扫描
        List<File> patternFiles = getSqlFilesByPattern(databaseType);
        strategyResults.put("PathMatchingResourcePatternResolver", patternFiles);
        
        // 策略2: 使用ClassLoader直接扫描
        List<File> classLoaderFiles = getSqlFilesByClassLoader(databaseType);
        strategyResults.put("ClassLoader", classLoaderFiles);
        
        // 策略3: 使用ResourceLoader扫描目录
        List<File> resourceLoaderFiles = getSqlFilesByResourceLoader(databaseType);
        strategyResults.put("ResourceLoader", resourceLoaderFiles);
        
        // 策略4: 硬编码路径测试
        List<File> hardcodedFiles = getSqlFilesByHardcoded(databaseType);
        strategyResults.put("硬编码路径", hardcodedFiles);
        
        // 打印各策略扫描结果
        log.info("各策略扫描结果:");
        for (Map.Entry<String, List<File>> entry : strategyResults.entrySet()) {
            log.info("  {}: 找到 {} 个文件", entry.getKey(), entry.getValue().size());
        }
        
        // 合并所有文件并去重
        Set<String> uniqueFileNames = new HashSet<>();
        List<File> allFiles = new ArrayList<>();
        
        for (Map.Entry<String, List<File>> entry : strategyResults.entrySet()) {
            String strategyName = entry.getKey();
            List<File> files = entry.getValue();
            
            for (File file : files) {
                if (uniqueFileNames.add(file.getName())) {
                    allFiles.add(file);
                    log.debug("添加文件 [{}] (来自策略: {})", file.getName(), strategyName);
                } else {
                    log.debug("跳过重复文件 [{}] (策略: {})", file.getName(), strategyName);
                }
            }
        }
        
        // 过滤和排序SQL文件
        List<File> validSqlFiles = filterAndSortSqlFiles(allFiles);
        
        log.info("扫描完成: 总共找到 {} 个唯一的SQL文件，其中 {} 个符合命名规范", 
                allFiles.size(), validSqlFiles.size());
        
        if (!validSqlFiles.isEmpty()) {
            log.info("符合规范的SQL文件列表（按执行顺序）:");
            for (File file : validSqlFiles) {
                log.info("  - {}", file.getName());
            }
        }
        
        return validSqlFiles;
    }
    
    /**
     * 过滤和排序SQL文件
     * 1. 过滤掉不符合命名规范的文件
     * 2. 按日期和流水号排序
     */
    private List<File> filterAndSortSqlFiles(List<File> allFiles) {
        List<SqlFileInfo> validSqlFiles = new ArrayList<>();
        List<String> invalidFiles = new ArrayList<>();
        
        // 过滤文件
        for (File file : allFiles) {
            String fileName = file.getName();
            
            if (isValidSqlFileName(fileName)) {
                SqlFileInfo fileInfo = parseSqlFileName(fileName, file);
                if (fileInfo != null) {
                    validSqlFiles.add(fileInfo);
                    log.debug("文件符合规范: {} -> 日期: {}, 流水号: {}", 
                            fileName, fileInfo.getFileDate(), fileInfo.getSequenceNumber());
                }
            } else {
                invalidFiles.add(fileName);
                log.warn("文件不符合命名规范，将被跳过: {}", fileName);
            }
        }
        
        // 按日期和流水号排序
        validSqlFiles.sort(SqlFileInfo::compareTo);
        
        // 记录排序结果
        if (!validSqlFiles.isEmpty()) {
            log.info("SQL文件排序结果:");
            for (int i = 0; i < validSqlFiles.size(); i++) {
                SqlFileInfo fileInfo = validSqlFiles.get(i);
                log.info("  {}: {} (执行顺序: {})", 
                        i + 1, fileInfo.getFileName(), fileInfo.toString());
            }
        }
        
        // 记录被跳过的文件
        if (!invalidFiles.isEmpty()) {
            log.warn("以下文件不符合命名规范，已被跳过:");
            for (String invalidFile : invalidFiles) {
                log.warn("  - {}", invalidFile);
            }
            log.info("命名规范: {} + YYYYMMDD + 流水号(2位) + {}", 
                    SQL_FILE_PREFIX, SQL_FILE_EXTENSION);
            log.info("示例: sh2025081901.sql");
        }
        
        // 返回排序后的File对象列表
        return validSqlFiles.stream()
                .map(SqlFileInfo::getFile)
                .collect(Collectors.toList());
    }
    
    /**
     * 移动SQL文件到已执行目录
     */
    public boolean moveToExecuted(String databaseType, File sqlFile) {
        try {
            // 获取项目根目录
            String projectRoot = getProjectRootPath();
            String executedPath = projectRoot + "/src/main/resources/" + config.getExecutedPath() + "/" + databaseType;
            
            // 创建目标目录
            File executedDir = new File(executedPath);
            if (!executedDir.exists()) {
                boolean created = executedDir.mkdirs();
                if (!created) {
                    log.error("无法创建目录: {}", executedPath);
                    return false;
                }
                log.info("已创建目录: {}", executedPath);
            }
            
            // 从classpath读取源文件内容
            String sourcePath = config.getUpgradePath() + "/" + databaseType + "/" + sqlFile.getName();
            Resource sourceResource = resourceLoader.getResource("classpath:" + sourcePath);
            
            if (!sourceResource.exists()) {
                log.error("源文件不存在: {}", sourcePath);
                return false;
            }
            
            // 读取文件内容
            String sqlContent;
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(sourceResource.getInputStream(), StandardCharsets.UTF_8))) {
                sqlContent = reader.lines().collect(Collectors.joining("\n"));
            }
            
            // 写入目标文件
            Path target = Paths.get(executedPath, sqlFile.getName());
            Files.write(target, sqlContent.getBytes(StandardCharsets.UTF_8));
            
            log.info("SQL文件已成功复制到已执行目录: {} -> {}", sqlFile.getName(), target);
            return true;
            
        } catch (IOException e) {
            log.error("移动SQL文件失败: {}, 错误信息: {}", sqlFile.getName(), e.getMessage(), e);
            return false;
        } catch (Exception e) {
            log.error("移动SQL文件时发生未知错误: {}, 错误信息: {}", sqlFile.getName(), e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * 删除SQL文件（从classpath中标记为已处理）
     */
    public boolean deleteSqlFile(File sqlFile) {
        try {
            // 由于classpath中的文件不能直接删除，我们通过复制到已执行目录来"删除"
            log.info("classpath中的文件无法直接删除，已通过移动到已执行目录来处理: {}", sqlFile.getName());
            return true;
        } catch (Exception e) {
            log.error("处理SQL文件时发生错误: {}, 错误信息: {}", sqlFile.getName(), e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * 读取SQL文件内容
     */
    public String readSqlContent(File sqlFile) {
        try {
            // 从classpath读取文件内容
            String filePath = config.getUpgradePath() + "/" + getDatabaseTypeFromPath(sqlFile.getPath()) + "/" + sqlFile.getName();
            Resource resource = resourceLoader.getResource("classpath:" + filePath);
            
            if (!resource.exists()) {
                log.error("SQL文件不存在: {}", filePath);
                return null;
            }
            
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
                return reader.lines().collect(Collectors.joining("\n"));
            }
            
        } catch (IOException e) {
            log.error("读取SQL文件内容失败: {}", e.getMessage(), e);
            return null;
        }
    }
    
    /**
     * 从文件路径中提取数据库类型
     */
    private String getDatabaseTypeFromPath(String filePath) {
        // 从路径中提取数据库类型，例如：sql/upgrade/mysql/file.sql -> mysql
        String[] parts = filePath.split("/");
        for (int i = 0; i < parts.length - 1; i++) {
            if (parts[i].equals("upgrade") && i + 1 < parts.length) {
                return parts[i + 1];
            }
        }
        return "mysql"; // 默认返回mysql
    }
    
    /**
     * 获取项目根目录路径
     */
    private String getProjectRootPath() {
        try {
            // 方法1: 尝试从classpath获取项目根目录
            Resource resource = resourceLoader.getResource("classpath:");
            if (resource.exists()) {
                File classpathDir = resource.getFile();
                // 向上查找直到找到包含pom.xml的目录
                File current = classpathDir;
                while (current != null && !new File(current, "pom.xml").exists()) {
                    current = current.getParentFile();
                }
                if (current != null) {
                    log.debug("从classpath找到项目根目录: {}", current.getAbsolutePath());
                    return current.getAbsolutePath();
                }
            }
        } catch (IOException e) {
            log.warn("无法从classpath获取项目根目录: {}", e.getMessage());
        }
        
        try {
            // 方法2: 尝试从当前工作目录查找
            String userDir = System.getProperty("user.dir");
            File currentDir = new File(userDir);
            
            // 检查当前目录或父目录是否包含pom.xml
            File current = currentDir;
            while (current != null && !new File(current, "pom.xml").exists()) {
                current = current.getParentFile();
            }
            if (current != null) {
                log.debug("从工作目录找到项目根目录: {}", current.getAbsolutePath());
                return current.getAbsolutePath();
            }
            
            // 如果找不到pom.xml，使用当前工作目录
            log.info("使用当前工作目录作为项目根目录: {}", userDir);
            return userDir;
            
        } catch (Exception e) {
            log.warn("无法从工作目录获取项目根目录: {}", e.getMessage());
        }
        
        // 方法3: 使用系统属性
        String userDir = System.getProperty("user.dir");
        log.info("使用系统属性中的工作目录: {}", userDir);
        return userDir;
    }
    
    /**
     * 验证目录结构
     */
    public boolean validateDirectoryStructure() {
        try {
            String projectRoot = getProjectRootPath();
            String upgradePath = projectRoot + "/src/main/resources/" + config.getUpgradePath();
            String executedPath = projectRoot + "/src/main/resources/" + config.getExecutedPath();
            
            File upgradeDir = new File(upgradePath);
            File executedDir = new File(executedPath);
            
            if (!upgradeDir.exists()) {
                log.warn("升级目录不存在，将创建: {}", upgradePath);
                upgradeDir.mkdirs();
            }
            
            if (!executedDir.exists()) {
                log.warn("已执行目录不存在，将创建: {}", executedPath);
                executedDir.mkdirs();
            }
            
            log.info("目录结构验证完成");
            return true;
            
        } catch (Exception e) {
            log.error("目录结构验证失败: {}", e.getMessage(), e);
            return false;
        }
    }
} 